﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WASapTKB.Domain
{
    public class GenericAlgorithm
    {
        private InputData inputData = InputData.Instance();
        public List<Individual> LstElements; // Quần thể.
        public Individual ScheduleResult;
        public int NumLoop = 0;
        /*********************************************************************************************/
        /*********************************************************************************************/
        #region Các chức năng chính của chương trình.
        // Khởi tạo quần thể. Tạo cá thể và đánh giá độ thích nghi của cá thể.
        public GenericAlgorithm()
        {
            LstElements = new List<Individual>();
            for (int i = 0; i < inputData.NumElements; i++)
            {
                Dictionary<int, int> lstUnitError = new Dictionary<int, int>();
                Individual schedule = new Individual(inputData.LstEvent);
                schedule.Id = i;
                Contraints(schedule);
                LstElements.Add(schedule);
            }
        }

        // Đánh giá độ thích nghi của cá thể và sắp xếp số lỗi của từng tiết tăng dần.
        int Contraints(Individual pSchedule)
        {
            //int result = iContraintRate1 * ContraintC1() + iContraintRate2 * ContraintC2(pSchedule) + iContraintRate3 * ContraintC3(pSchedule) + iContraintRate4 * ContraintC4(pSchedule) + iContraintRate5 * ContraintC5();
            //return result / 100;
            int iNumError = pSchedule.ConstraintC2() + pSchedule.ConstraintC3() + pSchedule.ConstraintC4();
            pSchedule.NumError = iNumError;
            // Sắp xếp số lỗi của từng tiết tăng dần.
            List<KeyValuePair<int,int>> lstTemp = pSchedule.PeriodErrors.OrderBy(p => p.Value).ToList();
            pSchedule.PeriodErrors.Clear();
            foreach (var item in lstTemp)
            {
                pSchedule.PeriodErrors.Add(item.Key, item.Value);
            }
            return pSchedule.NumError;
        }

        // Quá trình tìm kiếm thời khóa biểu.
        public bool SearchResult(int pNumErrorMax)
        {
            bool result = false;
            // Điều kiện dừng.
            for (NumLoop = 0; NumLoop < inputData.NumLoopMax; NumLoop++)
            {
                SelectElement();
                ScheduleResult = LstElements[0];
                if (ScheduleResult.NumError < pNumErrorMax)
                {
                    result = true;
                    break;
                }
                LaiGhep();
                DotBien();
            }
            return result;
        }

        // Sắp xếp cá thể trong quần thể theo thứ tự giảm dần độ thích nghi (tăng dần số vi phạm).
        void SortElements()
        {
            LstElements.Sort((p, q) => p.NumError.CompareTo(q.NumError));
        }

        // Chọn lựa cá thể.
        void SelectElement()
        {
            SortElements();
            while (LstElements.Count > inputData.NumElements)
            {
                LstElements.RemoveAt(LstElements.Count - 1);
            }
        }

        // Lai ghép 2 thời khóa biểu cho ra 2 thời khóa biểu mới.
        Individual LaiGhep(Individual pSchedule)
        {
            Individual result = new Individual();
            Random random = new Random();
            Dictionary<int, int> periodErrors = new Dictionary<int, int>(pSchedule.PeriodErrors);
            int iIndex = 0;
            for (int i = 0; i < inputData.NumWeekPeriodTotal; i++)
            {
                // Lấy ngâu nhiên 1 tiết.
                iIndex = random.Next(0, periodErrors.Count - 1);
                int iPeriod = periodErrors.ElementAt(iIndex).Key;
                // Khởi tạo giá trị sự kiện.
                int iPeriodError = -1;
                int iEventValue = pSchedule.Schedule[iPeriod];
                if (iEventValue != -1)
                    iPeriodError = 0;// Khởi tạo lại số lỗi.
                // Tạo mới sự kiện.
                result.Schedule.Add(iEventValue);
                result.PeriodErrors.Add(i, iPeriodError);
                // Loại bỏ tiết đã lấy giá trị.
                periodErrors.Remove(iPeriod);
            }// end for
            return result;
        }

        // Lai ghép quần thể. Lấy phân nửa thời khóa biểu đầu lai ghép với phân nửa sau.
        void LaiGhep()
        {
            int iCount = (inputData.NumElements * inputData.iLaiGhepRate) / 100;
            for (int i = 0; i < iCount; i++)
            {
                // Tạo mới thời khóa biểu.
                Individual child = LstElements[i];
                child = LaiGhep(child);
                Contraints(child);// Đánh giá độ thích nghi của thời khóa biểu mới.
                LstElements.Add(child);
            }
        }

        // Đột biến 1 thời khóa biểu.
        void DotBien(Individual pSchedule)
        {
            Random random = new Random();
            Dictionary<int, int> periodErrors = pSchedule.PeriodErrors.Where(p => p.Value != 0).ToDictionary(p => p.Key, p => p.Value);
            if (periodErrors.Count < 2)
                return;
            // Khởi tạo lại số lỗi lớn hơn 0 thành 0.
            Dictionary<int, int> periodErrorsTemp = periodErrors.Where(p => p.Value == 1).ToDictionary(p => p.Key, p => p.Value);
            for (int i = 0; i < periodErrorsTemp.Count; i++)
            {
                pSchedule.PeriodErrors[periodErrorsTemp.ElementAt(i).Key] = 0;
            }
            // Lấy 2 tiết ngẫu nhiên để hoán đổi vị trí.
            int iMax = periodErrors.Count - 1;
            int iCount = random.Next(1, iMax);// Lấy ngẫu nhiên số lần hoán đổi.
            for (int i = 0; i < iCount; i++)
            {
                // Lấy ngẫu nhiên 1 tiết trong các có lỗi.
                int iPeriod1 = random.Next(0, iMax);
                int iPeriod2 = random.Next(0, iMax);
                iPeriod1 = periodErrors.ElementAt(iPeriod1).Key;
                iPeriod2 = periodErrors.ElementAt(iPeriod2).Key;
                // Hoán đổi giá trị của 2 tiết.
                int iTemp = pSchedule.Schedule[iPeriod1];
                pSchedule.Schedule[iPeriod1] = pSchedule.Schedule[iPeriod2];
                pSchedule.Schedule[iPeriod2] = iTemp;
                // Hoán đổi số lỗi của 2 tiết.
                iTemp = pSchedule.PeriodErrors[iPeriod1];
                pSchedule.PeriodErrors[iPeriod1] = pSchedule.PeriodErrors[iPeriod2];
                pSchedule.PeriodErrors[iPeriod2] = iTemp;
                //if (pSchedule.PeriodErrors[iPeriod1] > 0) pSchedule.PeriodErrors[iPeriod1] = 0;
                //if (pSchedule.PeriodErrors[iPeriod2] > 0) pSchedule.PeriodErrors[iPeriod2] = 0;
            }// end for
        }

        // Đột biến một số cá thể trong quần thể.
        void DotBien()
        {
            int iCount = (inputData.NumElements * inputData.iDotBienRate) / 100;
            for (int i = inputData.NumElementsKeep; i < iCount; i++)
            {
                Individual schedule = LstElements[i];
                DotBien(schedule);
                Contraints(schedule);// Đánh giá lại độ thích nghi của thời khóa biểu mới.
            }
        }
        #endregion Các chức năng chính của chương trình.

    }// end class
}// end namespace
